package com.katze.boot.system.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.katze.boot.plugins.security.annotations.RestRequestMapping;
import com.katze.boot.plugins.security.domain.UserDetail;
import com.katze.boot.system.dao.LogMapper;
import com.katze.boot.system.domain.SysLog;
import com.katze.boot.system.service.LogService;
import com.katze.common.Pagination;
import com.katze.common.http.HttpUtils;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.StandardReflectionParameterNameDiscoverer;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.*;

@Service("logService")
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true, rollbackFor = Exception.class)
public class LogServiceImpl extends ServiceImpl<LogMapper, SysLog> implements LogService {

    @Resource
    private ObjectMapper objectMapper;

    @Override
    public IPage<SysLog> findLogs(Pagination request, SysLog sysLog) {
        try {
            QueryWrapper<SysLog> queryWrapper = new QueryWrapper<>();

            if (StringUtils.isNotBlank(sysLog.getUsername())) {
                queryWrapper.lambda().eq(SysLog::getUsername, sysLog.getUsername());
            }
            if (StringUtils.isNotBlank(sysLog.getOperation())) {
                queryWrapper.lambda().like(SysLog::getOperation, sysLog.getOperation());
            }
            if (StringUtils.isNotBlank(sysLog.getLocation())) {
                queryWrapper.lambda().like(SysLog::getLocation, sysLog.getLocation());
            }
            if (StringUtils.isNotBlank(sysLog.getCreateTimeFrom()) && StringUtils.isNotBlank(sysLog.getCreateTimeTo())) {
                queryWrapper.lambda()
                        .ge(SysLog::getCreateTime, sysLog.getCreateTimeFrom())
                        .le(SysLog::getCreateTime, sysLog.getCreateTimeTo());
            }
            if (StringUtils.isNotBlank(sysLog.getTimeFrame())) {
                String[] items = sysLog.getTimeFrame().split(",");
                queryWrapper.lambda().ge(SysLog::getCreateTime, items[0]).le(SysLog::getCreateTime, items[1]);
            }
            return this.page(request.toPage(OrderItem.desc("create_time")), queryWrapper);
        } catch (Exception e) {
            log.error("获取系统日志失败", e);
            return null;
        }
    }

    @Override
    @Transactional
    public void deleteLogs(String[] logIds) {
        List<String> list = Arrays.asList(logIds);
        baseMapper.deleteBatchIds(list);
    }

    @Override
    @Transactional
    public void around(ProceedingJoinPoint point, Object principal, long duration, HttpServletRequest request) throws Exception {
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        RestRequestMapping logAnnotation = method.getAnnotation(RestRequestMapping.class);
        if (logAnnotation == null || logAnnotation.skip()) return;

        String ip = HttpUtils.getRemoteAddress(request);
        SysLog log = new SysLog(ip, duration);
        if (principal instanceof UserDetail user) {
            log.setUserId(user.getUserId());
            log.setUsername(user.getUsername());
        }
        // 注解上的描述
        log.setOperation(logAnnotation.description());
        log.setMethod(point.getTarget().getClass().getName() + "." + signature.getName() + "()");
        // 请求的方法参数值
        Object[] args = point.getArgs();
        // 请求的方法参数名称
        StandardReflectionParameterNameDiscoverer u = new StandardReflectionParameterNameDiscoverer();
        String[] paramNames = u.getParameterNames(method);
        if (args != null && paramNames.length > 0) {
            StringBuilder params = new StringBuilder();
            params = handleParams(params, args, Arrays.asList(paramNames));
            log.setParams(params.toString());
        }
        log.setCreateTime(new Date());
        // 保存系统日志
        this.baseMapper.insert(log);
    }

    private StringBuilder handleParams(StringBuilder params, Object[] args, List<?> paramNames) throws JsonProcessingException {
        for (int i = 0; i < args.length; i++) {
            if (args[i] instanceof Map) {
                Set<?> set = ((Map<?, ?>) args[i]).keySet();
                List<Object> list = new ArrayList<>();
                List<Object> paramList = new ArrayList<>();
                for (Object key : set) {
                    list.add(((Map<?, ?>) args[i]).get(key));
                    paramList.add(key);
                }
                return handleParams(params, list.toArray(), paramList);
            } else {
                if (args[i] instanceof String[]){
                    params.append(" ").append(paramNames.get(i)).append(": ").append(String.join(",", (String[]) args[i]));
                } else if (args[i] instanceof Serializable) {
                    Class<?> aClass = args[i].getClass();
                    try {
                        aClass.getDeclaredMethod("toString", new Class[]{null});
                        // 如果不抛出 NoSuchMethodException 异常则存在 toString 方法 ，安全的 writeValueAsString ，否则 走 Object的 toString方法
                        params.append(" ").append(paramNames.get(i)).append(": ").append(objectMapper.writeValueAsString(args[i]));
                    } catch (NoSuchMethodException e) {
                        params.append(" ").append(paramNames.get(i)).append(": ").append(objectMapper.writeValueAsString(args[i].toString()));
                    }
                } else if (args[i] instanceof MultipartFile file) {
                    params.append(" ").append(paramNames.get(i)).append(": ").append(file.getName());
                } else {
                    params.append(" ").append(paramNames.get(i)).append(": ").append(args[i]);
                }
            }
        }
        return params;
    }


}